package org.myspringframework;

import org.dom4j.*;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ description
 * @ author wuyunpeng
 * @ create 2022-10-28 18:10
 */
public class ClassPathXmlApplicationContext implements ApplicationContext {

    private Map<String, Object> singletonObjects = new HashMap<>();

    /**
     * 在 ClassPathXmlApplicationContext 构造方法中实现逻辑
     *
     * @param configLocation
     */
    public ClassPathXmlApplicationContext(String configLocation) {
        try {
            //1、用SAXReader 解析xml文件
            SAXReader reader = new SAXReader();
            //2、获取类路径下的资源
            final InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation);
            //3、调用read方法读取xml文件，并转换为 Document 对象
            final Document document = reader.read(in);
            //4、通过Document对象获取指定名称的标签结点
            final List<Node> nodes = document.selectNodes("//bean");
            //5、第一次遍历nodes，将bean曝光（此时还没有属性赋值），仅仅是把bean封装到 HashMap 中
            //以解决 singleton + ref 循环依赖问题 (prototype + ref 这种情况是无法解决的，因为每次ref都会创建一个新的实例)
            //而解决这个问题的关键就是让 bean实例化 和 bean属性赋值 这两个步骤分开
            nodes.forEach(node -> {
                //向下转型，以便获取更丰富的方法
                final Element beanEle = (Element) node;
                //获取 bean 标签上的 id 属性 和 class 属性
                final String id = beanEle.attributeValue("id");
                final String aClass = beanEle.attributeValue("class");
                try {
                    final Class<?> clazz = Class.forName(aClass);
                    final Constructor<?> con = clazz.getDeclaredConstructor();
                    final Object instance = con.newInstance();
                    singletonObjects.put(id, instance);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            //6、再次遍历一遍nodes，这次主要是给 node 赋值
            nodes.forEach(node -> {
                final Element beanEle = (Element) node;
                final String id = beanEle.attributeValue("id");
                final String aClass = String.valueOf(beanEle.attributeValue("class"));
                //获取 bean标签 内容体内的 多个 property 属性
                final List<Element> properties = beanEle.elements("property");
                properties.forEach(property -> {
                    try {
                        final String name = property.attributeValue("name");    //属性名
                        String setMethodName = "set" + name.toUpperCase().charAt(0) + name.substring(1); //拼出set方法的名称
                        final Class<?> clazz = Class.forName(aClass);
                        final Field field = clazz.getDeclaredField(name);   // 获取名称为 name 的属性字段，根据字段类型，获取setMethod
                        final Method setMethod = clazz.getDeclaredMethod(setMethodName, field.getType()); //获取set方法
                        final Object obj = singletonObjects.get(id);        //需要设置属性的对象

                        final String value = property.attributeValue("value");  //基本数据类型
                        final String ref = property.attributeValue("ref");      //引用数据类型
                        //判断是 value 和 ref 的哪一种类型
                        if (value != null) {
                            // field.getType().getName ==> org. ..... . String
                            // field.getType().getSimpleName ==> String
                            final String type = field.getType().getSimpleName();
                            // 将 value 赋值给 type 属性类型的值
                            Object resValue = getResValue(type, value);
                            setMethod.invoke(obj, resValue);
                        }
                        if (ref != null) {
                            //因为在 xml 文件中，ref的值对应的就是相应 bean 对象的 id 值
                            //直接从 singletonObjects 中获取对应 id 的对象
                            final Object refValue = singletonObjects.get(ref);
                            setMethod.invoke(obj, refValue);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            });
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * 将 value 转换为指定类型 type 的值
     *
     * @param type
     * @param value
     * @return
     */
    private Object getResValue(String type, String value) {
        Object resValue = null;
        switch (type) {
            case "byte":
                resValue = Byte.parseByte(value);
                break;
            case "int":
                resValue = Integer.parseInt(value);
                break;
            case "short":
                resValue = Short.parseShort(value);
                break;
            case "long":
                resValue = Long.parseLong(value);
                break;
            case "float":
                resValue = Float.parseFloat(value);
                break;
            case "double":
                resValue = Double.parseDouble(value);
                break;
            case "boolean":
                resValue = Boolean.parseBoolean(value);
                break;
            case "char":
                resValue = value.charAt(0);
                break;
            case "Byte":
                resValue = Byte.valueOf(value);
                break;
            case "Short":
                resValue = Short.valueOf(value);
                break;
            case "Integer":
                resValue = Integer.valueOf(value);
                break;
            case "Long":
                resValue = Long.valueOf(value);
                break;
            case "Float":
                resValue = Float.valueOf(value);
                break;
            case "Double":
                resValue = Double.valueOf(value);
                break;
            case "Boolean":
                resValue = Boolean.valueOf(value);
                break;
            case "Character":
                resValue = value.charAt(0);
                break;
            case "String":
                resValue = value;
                break;
        }
        return resValue;
    }

    /**
     * 根据 bean的 id (beanName) 获取 Bean 对象
     *
     * @param beanName
     * @return
     */
    @Override
    public Object getBean(String beanName) {
        return singletonObjects.get(beanName);
    }
}
